//go:generate go run main.go
package main

import (
	"fmt"
	"os"
	"path/filepath"
	"strings"
	"text/template"
)

// TestCase defines a single ratelimit test configuration
type TestCase struct {
	NodeCount   int
	Limit       int64
	Duration    int64
	LoadFactor  float64
	WindowCount int
}

var extremeEdgeCases = []TestCase{
	{NodeCount: 9, Limit: 100, Duration: 30000, LoadFactor: 2.0, WindowCount: 5},
	{NodeCount: 9, Limit: 100, Duration: 300000, LoadFactor: 1.0, WindowCount: 3},
}

var realisticCombinations = []TestCase{
	{NodeCount: 1, Limit: 100, Duration: 10000, LoadFactor: 0.9, WindowCount: 10},
	{NodeCount: 3, Limit: 100, Duration: 10000, LoadFactor: 0.9, WindowCount: 10},
	{NodeCount: 5, Limit: 100, Duration: 10000, LoadFactor: 0.9, WindowCount: 10},

	{NodeCount: 1, Limit: 100, Duration: 10000, LoadFactor: 1.0, WindowCount: 10},
	{NodeCount: 3, Limit: 100, Duration: 10000, LoadFactor: 1.0, WindowCount: 10},
	{NodeCount: 5, Limit: 100, Duration: 10000, LoadFactor: 1.0, WindowCount: 10},

	{NodeCount: 1, Limit: 100, Duration: 10000, LoadFactor: 2.0, WindowCount: 10},
	{NodeCount: 3, Limit: 100, Duration: 10000, LoadFactor: 2.0, WindowCount: 10},
	{NodeCount: 5, Limit: 100, Duration: 10000, LoadFactor: 2.0, WindowCount: 10},

	{NodeCount: 1, Limit: 100, Duration: 300000, LoadFactor: 0.9, WindowCount: 10},
	{NodeCount: 3, Limit: 100, Duration: 300000, LoadFactor: 0.9, WindowCount: 10},
	{NodeCount: 5, Limit: 100, Duration: 300000, LoadFactor: 0.9, WindowCount: 10},

	{NodeCount: 3, Limit: 100, Duration: 300000, LoadFactor: 1.5, WindowCount: 10},
	{NodeCount: 5, Limit: 100, Duration: 300000, LoadFactor: 1.5, WindowCount: 10},

	{NodeCount: 1, Limit: 10, Duration: 60000, LoadFactor: 0.9, WindowCount: 10},
	{NodeCount: 3, Limit: 10, Duration: 60000, LoadFactor: 0.9, WindowCount: 10},
	{NodeCount: 5, Limit: 10, Duration: 60000, LoadFactor: 0.9, WindowCount: 10},
	{NodeCount: 3, Limit: 10, Duration: 60000, LoadFactor: 1.5, WindowCount: 10},
	{NodeCount: 5, Limit: 10, Duration: 60000, LoadFactor: 1.5, WindowCount: 10},
}

// BuildTag returns the go:build constraint line (or empty) appropriate for tc.
// It is used by the generator template to decide which tests are compiled by default.
func (tc TestCase) BuildTag() string {
	switch {
	// Single nodes always run
	case tc.NodeCount == 1:
		return "//go:build integration"
	// NodeCount up to and including 5 uses the integration_long tag
	case tc.NodeCount <= 5:
		return "//go:build integration_long"
	// Assume its a stress test to ensure fast tests stay fast
	default:
		return "//go:build stress"
	}
}

func (tc TestCase) PackageName() string {
	return fmt.Sprintf("ratelimit_nodes%02d_limit%04d_duration%09d_load%s_windows%03d",
		tc.NodeCount,
		tc.Limit,
		tc.Duration,
		strings.ReplaceAll(fmt.Sprintf("%05.2f", tc.LoadFactor), ".", "_"),
		tc.WindowCount,
	)
}

func (tc TestCase) TestName() string {
	return fmt.Sprintf("TestIntegration_RateLimit_Nodes%02d_Limit%04d_Duration%09d_Load%s_Windows%03d",
		tc.NodeCount,
		tc.Limit,
		tc.Duration,
		strings.ReplaceAll(fmt.Sprintf("%05.2f", tc.LoadFactor), ".", "_"),
		tc.WindowCount,
	)
}

const testTemplate = `// Code generated by go generate; DO NOT EDIT.{{ if .BuildTag }}
{{ .BuildTag }}
{{ end }}
package {{ .PackageName }}

import (
	"testing"

	"github.com/unkeyed/unkey/go/apps/api/integration"
	run "github.com/unkeyed/unkey/go/apps/api/integration/multi_node_ratelimiting"
	"github.com/unkeyed/unkey/go/pkg/testutil"
)

func {{ .TestName }}(t *testing.T) {
	testutil.SkipUnlessIntegration(t)

	h := integration.New(t, integration.Config{
		NumNodes: {{ .NodeCount }},
	})

	run.RunRateLimitTest(
		t,
		h,
		{{ .Limit }},            // limit
		{{ .Duration }},         // duration
		{{ .WindowCount }},      // window count
		{{ .LoadFactor }},       // load factor
		{{ .NodeCount }},        // node count
	)
}
`

func main() {
	// Create base directory for generated tests
	baseDir := "../generated"
	err := os.RemoveAll(baseDir)
	if err != nil {
		panic(err)
	}
	err = os.MkdirAll(baseDir, 0o755)
	if err != nil {
		panic(err)
	}

	// Parse template
	tmpl, err := template.New("test").Parse(testTemplate)
	if err != nil {
		panic(err)
	}

	// Combine all test cases
	testCases := make([]TestCase, 0, len(realisticCombinations)+len(extremeEdgeCases))
	testCases = append(testCases, realisticCombinations...)
	testCases = append(testCases, extremeEdgeCases...)

	// Generate test files
	for _, tc := range testCases {
		packageDir := filepath.Join(baseDir, tc.PackageName())
		err := os.MkdirAll(packageDir, 0o755)
		if err != nil {
			panic(err)
		}

		filePath := filepath.Join(packageDir, "generated_test.go")
		file, err := os.Create(filePath)
		if err != nil {
			panic(err)
		}

		err = tmpl.Execute(file, tc)
		if err != nil {
			panic(err)
		}

		err = file.Close()
		if err != nil {
			panic(err)
		}
	}

	fmt.Printf("Generated %d test cases (%d realistic + %d extreme)\n",
		len(testCases), len(realisticCombinations), len(extremeEdgeCases))
}
